/*
 * @Author: BUG yangcheng960813@163.com
 * @Date: 2024-05-17 10:15:56
 * @LastEditTime: 2024-07-12 13:02:16
 * @LastEditors: yayalee yangcheng960813@163.com
 * @Description: 
 * @FilePath: \unfinished-challenges\assets\core_ts\ObjectUtil.ts
 * 可以输入预定的版权声明、个性签名、空行等
 */

import { forEach, isArray, isEmpty, isNull, isObject, isString } from "./TsUtil";

export class ObjectUtil {

    /** 
    * Used to match backslashes in property paths. 
    * 用于匹配属性路径中的反斜杠。
    */
    private static reEscapeChar = /\\(\\)?/g;
    /** 
     * Used to match property names within property paths. 
     * 用于匹配属性路径中的属性名称。
     */
    private static rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;

    /** 
     * Used as references for various `Number` constants. 
     * 用作各种' Number '常量的引用
     */
    private static MAX_SAFE_INTEGER = 9007199254740991;
    /** 
     * Used to detect unsigned integer values. 
     * 用于检测无符号整数值
     */
    private static reIsUint = /^(?:0|[1-9]\d*)$/;

    /**字符串解析成数组 a.b.c = [a, b, c] 或字符串 a.b[c] = [a, b, c]
     * @description: 
     * @param {string} string
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     * ```
     * toPath("a.b.c")
     * toPath("a.b[c]")
     * ```
     */
    public static toPath(string: string): string[] {
        var result = [];
        if (string.charCodeAt(0) === 46 /* . */) {
            result.push('');
        }
        string.replace(ObjectUtil.rePropName, function (match, number, quote, subString) {
            result.push(quote ? subString.replace(ObjectUtil.reEscapeChar, '$1') : (number || match));
            return number || match;
        });
        return result;
    }

    /**
     * @description: 
     * @param {number} value
     * @param {number} length
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     */
    private static isIndex(value: number, length?: number): boolean {
        var type = typeof value;
        length = length == null ? ObjectUtil.MAX_SAFE_INTEGER : length;
        return !!length &&
            (type == 'number' ||
                (type != 'symbol' && ObjectUtil.reIsUint.test(value.toString()))) &&
            (value > -1 && value % 1 == 0 && value < length);
    }

    /**
     * @description: 根据 object对象的path路径获取值。 如果解析 value 是 undefined 会以 defaultValue 取代。
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     * ```
     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
     *  get(object, 'a[0].b.c');
     *  // => 3
     *  get(object, ['a', '0', 'b', 'c']);
     *  // => 3
     *  get(object, 'a.b.c', 'default');
     *  // => 'default'
     * ```
     */
    public static get<T>(object: any, path: string | number, defaultValue?: T): T | undefined;
    public static get<T>(object: any, path: (string | number)[], defaultValue?: T): T | undefined;
    public static get<T>(object: any, path: any, defaultValue?: any): T | undefined;
    public static get<T>(object: any, path: any, defaultValue?: T): T | undefined {
        path = ObjectUtil.toPath(path)
        let obj = object;
        for (const key of path) {
            if (!obj) return defaultValue;
            obj = obj[key];
        }
        return isNull(obj) ? defaultValue : obj;
    }

    /**
     * @description: 设置 object对象中对应 path 属性路径上的值，如果path不存在，则创建。 
     * 缺少的索引属性会创建为数组，而缺少的属性会创建为对象。
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     * ```
     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
     * set(object, 'a[0].b.c', 4);
     * console.log(object.a[0].b.c);
     * // => 4
     * set(object, ['x', '0', 'y', 'z'], 5);
     * console.log(object.x[0].y.z);
     * ```
     */
    public static set<T, U, W>(object: T, path: string | number, value: U, customizer?: (value: W, key: string, object: T) => T): T;
    public static set<T, U, W>(object: T, path: (string | number)[], value: U, customizer?: (value: W, key: string, object: T) => T): T;
    public static set<T>(object: T, path: any, value: T, customizer?: (value: T, key: string, object: any) => T): T {
        path = ObjectUtil.toPath(path);
        var index = -1,
            length = path.length,
            lastIndex = length - 1,
            nested: any = object;

        while (nested != null && ++index < length) {
            var key = path[index],
                newValue: any = value;
            //还不是最后一个
            if (index != lastIndex) {
                var objValue = nested[key];
                newValue = customizer ? customizer(objValue, key, nested) : undefined;
                if (newValue === undefined) {
                    //创建新对象
                    newValue = isObject(objValue) ? objValue : (ObjectUtil.isIndex(path[index + 1]) ? [] : {});
                }
            }
            //需要把 newValue 的值赋值过去
            nested[key] = newValue;
            //递归赋值
            nested = nested[key];
        }
        return object;
    }

    /**
     * @description: 创建一个数组，值来自 object 的paths路径相应的值
     * @param object 要迭代的对象
     * @param paths (...(string|string[])): 要获取的对象的元素路径，单独指定或者指定在数组中。
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     */
    public static at(object: any, path: string | string[]): [];
    public static at(object: any, path: any) {
        if (isArray(path)) {
            let arr: any = [];
            forEach(path, (p) => {
                arr.push(ObjectUtil.get(object, p));
            });
            return arr;
        }
        return [ObjectUtil.get(object, path)];
    }

    /**
     * @description: 检查 path 是否是object对象的直接属性。
     * @param object 要检索的对象。
     * @param path 要检查的路径path。
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     * ```
     * var object = { 'a': { 'b': 2 } };
     * var other = _.create({ 'a': _.create({ 'b': 2 }) });
     * has(object, 'a');
     * // => true
     * has(object, 'a.b');
     * // => true
     * has(object, ['a', 'b']);
     * // => true
     * _.has(other, 'a');
     * // => false
     * ```
     */
    public static has(object: any, path: string | number): boolean;
    public static has(object: any, path: (string | number)[]): boolean;
    public static has(object: any, path: any): boolean {
        return !isEmpty(ObjectUtil.get(object, path));
    }

    /**
     * @description: 
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     * ```
     * size([1, 2, 3]);
     * // => 3
     * size({ 'a': 1, 'b': 2 });
     * // => 2
     * size('pebbles');
     * // => 7
     * ```
     */
    public static size<T>(obj: Array<T>): number;
    public static size(obj: Object): number;
    public static size(obj: string): number;
    public static size(obj: any): number;
    public static size(obj: any): number {
        if (isArray(obj) || isString(obj)) {
            return obj.length;
        } else if (isObject(obj)) {
            return Object.keys(obj).length;
        }
        console.warn("类型错误", typeof obj);
        return 0;
    }

    /**
     * @description: 赋值操作 跟 Object.assign 一样
     * 但是我这个会递归检索 sources对象的值 并且查值是否存在不存在就用 origin 的默认值
     * 注意: 这方法会改变 origin object.
     * @return {*}
     * @author: yayalee
     * @since: v1.0.0
     */
    public static defaults<T, U>(origin: T, sources: U): T & U;
    public static defaults<T, U, V>(origin: T, sources: U, sources2: V): T & U & V;
    public static defaults<T, U, V, W>(origin: T, sources: U, sources2: V, sources3: W): T & U & V & W;
    public static defaults<T>(origin: T | any, ...sources: any[]): T & any {
        if (isNull(origin) || !isObject(origin)) {
            //必须得创建带有原型的对象不然会报错暂时先这样处理
            origin = Object.create({});
        }
        sources.forEach(nextSources => {
            //遍历 nextSources 对象
            for (let key in nextSources) {
                // origin 没有nextSources key 的属性 就给nextSources 赋值过去
                if (origin.hasOwnProperty(key)) {
                    if (isObject(origin[key])) {
                        //递归
                        origin[key] = ObjectUtil.defaults(origin[key], nextSources[key]);
                    } else {
                        origin[key] = !isNull(nextSources[key]) && nextSources[key] || origin[key];
                    }
                } else if (!isNull(nextSources[key])) {
                    // origin 没有这个属性 直接给 nextSources的值 赋值过来
                    origin[key] = nextSources[key];
                }
            }
        })
        return origin;
    }

    /**
     * @description: 从对象中移除所有可枚举属性
     * @param {any} obj
     * @author: yayalee
     * @since: v1.0.0
     */
    public static clear(obj: any) {
        Object.keys(obj).forEach(key => {
            delete obj[key];
        });
    }

}